// Copyright 2019 The rhyme_lph Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// s [author] rhyme_lph
// D [email]  rhymelph@gmail.com
// k [github] https://github.com/rhymelph

import 'dart:async';

import 'package:base_plugin/base_plugin.dart';
import 'package:finance_app/generated/l10n.dart';
import 'package:finance_app/src/common/config.dart';
import 'package:finance_app/src/entities/bill_detail_entity.dart';
import 'package:finance_app/src/entities/bill_entity.dart';
import 'package:finance_app/src/entities/business_contact_entity.dart';
import 'package:finance_app/src/entities/format/service_and_product_entity.dart';
import 'package:finance_app/src/entities/ocr_attachment_entity.dart';
import 'package:finance_app/src/entities/purchase_order_detail_entity.dart';
import 'package:finance_app/src/entities/purchase_order_entity.dart';
import 'package:finance_app/src/entities/setting_data_entity.dart';
import 'package:finance_app/src/http_service/http_bill_service.dart';
import 'package:finance_app/src/mixin/apply_credit_mixin.dart';
import 'package:finance_app/src/mixin/check_plan_mixin.dart';
import 'package:finance_app/src/mixin/discount_mixin.dart';
import 'package:finance_app/src/mixin/form_products_or_services_mixin.dart';
import 'package:finance_app/src/mixin/from_attach_mixin.dart';
import 'package:finance_app/src/mixin/init_edit_data_mixin.dart';
import 'package:finance_app/src/provider/dashboard/dashboard_view_provider.dart';
import 'package:finance_app/src/provider/dashboard/purchase/new_purchase_order_provider.dart';
import 'package:finance_app/src/provider/dashboard/purchase/purchases_provider.dart';
import 'package:finance_app/src/provider/dashboard/purchase/views/purchases_bill_provider.dart';
import 'package:finance_app/src/provider/dashboard/purchase/views/purchases_expense_provider.dart';
import 'package:finance_app/src/provider/dashboard/purchase/views/purchases_po_provider.dart';
import 'package:finance_app/src/provider/dashboard/purchase/views/purchases_summary_provider.dart';
import 'package:finance_app/src/router_gen/router_gen_providers.dart';
import 'package:finance_app/src/utils/cash_utils.dart';
import 'package:finance_app/src/utils/date_time_utils.dart';
import 'package:finance_app/src/utils/permission_utils.dart';
import 'package:finance_app/src/widgets/bottom_sheet/calendar_bottom_sheet.dart';
import 'package:finance_app/src/widgets/bottom_sheet/currency_bottom_sheet.dart';
import 'package:finance_app/src/widgets/bottom_sheet/delete_info_bottom_sheet.dart';
import 'package:finance_app/src/widgets/bottom_sheet/got_it_tip_bottom_sheet.dart';
import 'package:finance_app/src/widgets/bottom_sheet/judge_bottom_sheet.dart';
import 'package:flutter/material.dart';

class NewBillProvider extends BaseProvider<BillDetailEntity>
    with
        FormAttachMixin<BillDetailEntity>,
        FormProductsOrServicesMixin<BillDetailEntity>,
        DiscountMixin<BillDetailEntity>,
        ApplyCreditMixin<BillDetailEntity>,
        InitEditDataMixin<BillDetailEntity>,
        CheckPlanMixin {
  bool isSelectMode = false;

  TextEditingController billNumberController = TextEditingController();
  FocusNode billNumberFocusNode = FocusNode();

  TextEditingController notesController = TextEditingController();
  FocusNode notesFocusNode = FocusNode();

  DateTime date;

  DateTime dueDate;

  SettingDataCategory category;

  SettingDataCurrency currency;

  PurchaseOrderEntity purchasesOrder;

  BusinessContactEntity supplier;

  bool isDelivered = false;

  bool isClosed = false;

  bool isBill = true;

  @override
  void dispose() {
    super.dispose();
    billNumberController.dispose();
    billNumberFocusNode.dispose();
    notesController.dispose();
    notesFocusNode.dispose();
  }

  List<String> menuList = ['Edit', 'Delete'];

  void onSelectMenu(String value) {
    switch (value) {
      case 'Edit':
        onChangeEditMode();
        break;
      case 'Delete':
        onDelete();
        break;
      case 'See Payment':
        onSeePayment();
        break;
      case 'Make Payment':
        onMakePayment();
        break;
      case 'Mark As Delivered':
        onMarkAsDelivered();
        break;
      case 'Share':
        onShare();
        break;
    }
  }

  @override
  void initState() {
    usePurchasePrice = true;
    super.initState();
  }

  void unFocus() {
    billNumberFocusNode.unfocus();
    notesFocusNode.unfocus();
  }

  void onSave() {
    unFocus();

    if (supplier == null) {
      showToast(S.current.please_select_supplier_name);
      return;
    }

    if (billNumberController.text.length == 0) {
      showToast(S.current.please_enter_the_bill_number);
      return;
    }

    if (date == null) {
      showToast(S.current.please_select_issue_date);
      return;
    }

    if (dueDate == null) {
      showToast(S.current.please_select_due_date);
      return;
    }
    if (currency == null) {
      showToast(S.current.please_select_currency);
      return;
    }
    if (serviceAndProductList.length == 0) {
      showToast(S.current.please_add_item);
      return;
    }

    if (availableCredit > 0 && creditUsed == null && !isEdit) {
      obtainContext?.call((context) {
        onAskApplyCredit(onContinue: () {
          onSaveAPI();
        });
      });
      return;
    }
    onSaveAPI();
  }

  void onSaveAPI() async {
    List item = serviceAndProductList
        .map((e) => {
              'type': e.product != null ? 'product' : 'service',
              'type_id': e.id,
              'name': e.name,
              'quantity': e.number,
              'price': e.price,
              'is_tax_inclusive': e.isInclusive ? 1 : 0,
              'tax_id': e.tax.id,
              'tax_amount': e.taxAmount,
              'discount_type': e.discountType,
              'discount_value': e.discountValue,
            })
        .toList();
    List<int> attachmentIds = await uploadFiles();
    if (attachmentIds == null) return;

    BillEntity orderEntity;
    if (isEdit == true) {
      await post(
          HttpBillService.updateBill(
            data.id,
            purchase_order_id: purchasesOrder?.id,
            purchase_order_number: purchasesOrder?.number,
            contact_id: supplier?.id,
            category_id: category?.id,
            number: billNumberController.text,
            issued_at: DateTimeUtils.formatDateTime(date, 'yyyy-MM-dd'),
            due_at: DateTimeUtils.formatDateTime(dueDate, 'yyyy-MM-dd'),
            currency_code: currency?.key,
            subtotal: subTotal,
            exclusive_tax_amount: gst,
            inclusive_tax_amount: includesGST,
            discount_type: discountType,
            discount_amount: discountAmount,
            credit_used: creditUsed,
            total: totalDiscount,
            notes: notesController.text,
            item: item,
            attachment_ids: attachmentIds,
          ),
          autoHandler: AutoHandler.Toast, onSuccess: (result) {
        orderEntity = getEntityFromResult<BillEntity>(result);
      });
    } else {
      await post(
          HttpBillService.createBill(
            purchase_order_id: purchasesOrder?.id,
            purchase_order_number: purchasesOrder?.number,
            contact_id: supplier?.id,
            category_id: category?.id,
            number: billNumberController.text,
            issued_at: DateTimeUtils.formatDateTime(date, 'yyyy-MM-dd'),
            due_at: DateTimeUtils.formatDateTime(dueDate, 'yyyy-MM-dd'),
            currency_code: currency?.key,
            subtotal: subTotal,
            exclusive_tax_amount: gst,
            inclusive_tax_amount: includesGST,
            discount_type: discountType,
            discount_amount: discountAmount,
            credit_used: creditUsed,
            total: totalDiscount,
            notes: notesController.text,
            item: item,
            attachment_ids: attachmentIds,
          ),
          autoHandler: AutoHandler.Toast, onSuccess: (result) {
        orderEntity = getEntityFromResult<BillEntity>(result);
      });
    }

    if (orderEntity != null) {
      updateDashboard();
      if (isSelectMode == true) {
        obtainContext?.call((context) {
          Navigator.of(context).pop(orderEntity);
        });
      } else {
        id = orderEntity.id;
        await post(editDataFuture,
            loadingTip: S.current.loading,
            autoHandler: AutoHandler.Toast, onSuccess: (result) {
          isEdit = false;
          data = transformData(
              getEntityFromResult<BillDetailEntity>(result), false);
          update();
        });
      }
    }
  }

  void onChangeSupplierName() async {
    unFocus();
    final result = await Routes.navigateTo(
        RouterGenProviders.businessContactsPage,
        params: {
          'select': supplier,
        });
    if (result != null) {
      supplier = result;
      purchasesOrder = null;
      creditValue = null;
      availableCredit = supplier.totalUnpaidDebit;
      currencyName = currency.key;
      update();
    }
  }

  void onChangeIssueDate() {
    unFocus();
    obtainContext?.call((context) async {
      date = await showCalendarBottomSheet(context, date);
      update();
    });
  }

  void onChangeDueDate() {
    unFocus();
    obtainContext?.call((context) async {
      dueDate = await showCalendarBottomSheet(context, dueDate,
          startDate: date, withinFinancialYear: false);
      update();
    });
  }

  void onChangeBillCategory() async {
    unFocus();

    final result =
        await Routes.navigateTo(RouterGenProviders.selectCategoryPage, params: {
      'isPop': true,
    });
    if (result != null) {
      category = result;
      update();
    }
  }

  void onChangeCurrency() {
    obtainContext?.call((context) async {
      currency = await showCurrencyBottomSheet(context, currency);
      update();
    });
  }

  void onChangePurchasesOrderNumber() async {
    PurchaseOrderEntity result = await Routes.navigateTo(
        RouterGenProviders.purchaseOrderNumberPage,
        params: {'select': purchasesOrder, 'contactId': supplier?.id});
    if (result != null) {
      purchasesOrder = result;
      serviceAndProductList =
          purchasesOrder.items.map((e) => e.toServiceAndProduct()).toList();
      if (supplier == null) {
        supplier = purchasesOrder.contact;
      }
      update();
    }
  }

  void onSelectItem(ServiceAndProductEntity value) async {
    final result =
        await Routes.navigateTo(RouterGenProviders.quantityPage, params: {
      'product': value.product,
      'service': value.services,
      'initialNumber': value.number,
      'usePurchasePrice': true,
    });
    if (result != null) {
      bool delete = result['delete'];
      if (delete == true) {
        serviceAndProductList.remove(value);
      } else {
        value.number = result['data'].number;
      }
      update();
    }
  }

  @override
  Future get editDataFuture => HttpBillService.showBill(id);

  @override
  FutureOr<BillDetailEntity> transformData(BillDetailEntity d, bool isCache) {
    supplier = d.contact;
    billNumberController.text = d.number;
    date = DateTimeUtils.toDateTime(d.issuedAt);
    dueDate = DateTimeUtils.toDateTime(d.dueAt);
    purchasesOrder = PurchaseOrderEntity()
      ..id = d.purchaseOrderId
      ..number = d.purchaseOrderNumber;
    category = d.category;
    isDelivered = d.deliveredAt != null;
    isClosed = d.status == 'closed';
    currency = Config.settingData.currency.firstWhere(
        (element) => element.key == d.currencyCode,
        orElse: () => null);
    serviceAndProductList =
        d.items.map((e) => e.toServiceAndProduct()).toList();
    notesController.text = d.notes;
    if (d.discountType != null) {
      if (d.discountType == 'value') {
        discountValue = CashUtils.formatCashAmount2(d.discountAmount);
      } else {
        discountValue = '${d.discountAmount}%';
      }
    }
    if (d.creditUsed != 0) {
      creditValue = CashUtils.formatCashAmount2(d.creditUsed);
    }

    if (isClosed) {
      menuList = [
        // 'Edit',
        // 'Share',
        'See Payment'
      ];
    } else {
      menuList = ['Edit', 'Delete', 'Make Payment'];
      if (d.deliveredAt == null) {
        menuList.add('Mark As Delivered');
      }
      if (d.total != (d.unpaid + (creditUsed ?? 0))) {
        menuList.add('See Payment');
        menuList.remove('Delete');
        menuList.remove('Edit');
      }
    }

    if (isDelivered) {
      menuList.remove('Edit');
    }

    if (!Config.hasPermission(PermissionUtils.cashBank)) {
      menuList.remove('See Payment');
      menuList.remove('Make Payment');
    }
    handleDataUpdate(d);
    return super.transformData(d, isCache);
  }

  void onSeePayment() async {
    await Routes.navigateTo(RouterGenProviders.seePaymentPage, params: {
      'billId': data.id,
      'billNumber': data.number,
      'isClosed': isClosed,
    });
    loadData();
    updateDashboard();
  }

  void onDelete() {
    if (isPlanExpired()) return;
    obtainContext?.call((context) async {
      bool isSuccess = await showDeleteTransactionInfoBottomSheet(context);
      if (isSuccess == true) {
        isSuccess = false;
        await post(HttpBillService.deleteBill(data.id),
            loadingTip: S.current.loading,
            autoHandler: AutoHandler.Toast, onSuccess: (result) {
          isSuccess = true;
        });
        if (isSuccess == true) {
          await showDeleteSuccessTipBottomSheet(context);
          Navigator.of(context).pop({
            'delete': true,
          });
          updateDashboard();
          Config.dashboardProvider
              .onRefreshDashboard(refreshTransaction: false);
        }
      }
    });
  }

  void onMakePayment() async {
    if (isPlanExpired()) return;
    BillEntity bill = BillEntity()
      ..id = data.id
      ..issuedAt = data.issuedAt
      ..number = data.number;
    final result =
        await Routes.navigateTo(RouterGenProviders.newCashOutPage, params: {
      'billList': [bill],
      'total': data.unpaid.toDouble(),
      'currency': currency,
      'paidTo': supplier,
      'isSelectMode': true,
    });
    if (result != null) {
      onTry();
      updateDashboard();

      //刷新purchase order
      final poProvider = Providers.get<NewPurchaseOrderProvider>(
          RouterGenProviders.newPurchaseOrderPage, null);
      if (poProvider != null) {
        poProvider.onTry();
      }
    }
  }

  void updateDashboard() {
    Providers.get<PurchasesProvider>(RouterGenProviders.purchasesPage, null)
        ?.getChildProvider<PurchasesSummaryProvider>()
        ?.loadData();
    Providers.get<PurchasesProvider>(RouterGenProviders.purchasesPage, null)
        ?.getChildProvider<PurchasesPoProvider>()
        ?.loadData();
    Providers.get<PurchasesProvider>(RouterGenProviders.purchasesPage, null)
        ?.getChildProvider<PurchasesBillProvider>()
        ?.loadData();
    Providers.get<PurchasesProvider>(RouterGenProviders.purchasesPage, null)
        ?.getChildProvider<PurchasesExpenseProvider>()
        ?.loadData();
  }

  void initConvert(PurchaseOrderDetailEntity convert) {
    if (convert == null) return;

    supplier = convert.contact;
    date = DateTimeUtils.toDateTime(convert.issuedAt);
    dueDate = DateTimeUtils.toDateTime(convert.deliveryAt);
    purchasesOrder = PurchaseOrderEntity()
      ..id = convert.id
      ..number = convert.number;
    currency = Config.settingData.currency.firstWhere(
        (element) => element.key == convert.currencyCode,
        orElse: () => null);
    serviceAndProductList =
        convert.items.map((e) => e.toServiceAndProduct()).toList();
    notesController.text = convert.notes;
  }

  @override
  List<OcrAttachmentEntity> get attachments => data?.attachments;

  @override
  String get type => 'bill';

  @override
  TextEditingController get numberCtl => billNumberController;

  void onMarkAsDelivered() {
    if (isPlanExpired()) return;
    obtainContext?.call((context) async {
      bool isSuccess =
          await showMarkTheItemsAsDeliveredAsJudgeBottomSheet(context);
      if (isSuccess == true) {
        isSuccess = false;
        await post(HttpBillService.deliver(id),
            loadingTip: S.current.loading,
            autoHandler: AutoHandler.Toast, onSuccess: (result) {
          isSuccess = true;
        });
        if (isSuccess == true) {
          await showMarkedAsDeliveredSuccessTipBottomSheet(context);
          onTry();
        }
      }
    });
  }
}
